home *** CD-ROM | disk | FTP | other *** search
/ MacFormat 1995 January / macformat-020.iso / Shareware City / Applications / Alpha.5.96 folder / Tcl / SystemCode / bibtex.tcl < prev    next >
Encoding:
Text File  |  1994-09-19  |  29.3 KB  |  956 lines  |  [TEXT/ALFA]

  1. ###########################################################################
  2. # bibtex.tcl
  3. # This file contains a package of Tcl routines that add support for using
  4. # and maintaining BibTeX citation databases to Alpha. 
  5. #
  6. # See the accompanying file, "BibTeX Help", for a complete description.
  7. ###########################################################################
  8. # Notes: 
  9. # By default, only the required fields are included when a new bib entry 
  10. # is created.  You can select any other set of fields to be used by adding 
  11. # an appropriate entry to the 'myFld' array, following the example for the 
  12. # Article entry, further below.  You shouldn't change the 'rqdFld' or 
  13. # 'optFld' arrays, since these will (some day) be used for syntax checking.
  14. ###########################################################################
  15. # written by Tom Pollard (pollard@cucbs.chem.columbia.edu)
  16. #
  17. # Version History
  18. #
  19. # 1.9 (9/94)    'getFields' should now correctly parse any legal entry.
  20. #                'language' field now included.
  21. #                Default values for new fields (eg 'language') may be defined
  22. #                'preferBraces' replaced by 'fieldBraces' and 'entryBraces'.
  23. #                line-wrapping is done on reformatted entries.
  24. #                '@string' entries preserved in sorts.
  25. #                text before first entry and after last entry are preserved
  26. #                    by sorts.
  27. # 1.8 (8/94)    "getEntry" now recognizes parens as entry delimiters
  28. # 1.7 (8/94)    Bug fixes and accomodations to latex.tcl v2.2
  29. #               Template insertion streamlined
  30. #                Choose multiple fields at a time from list dialog
  31. # 1.6 (8/94)    "preferBraces" allows braces or quotes to be default for
  32. #                   new or reformatted entries,
  33. #               Menu built using $entryNames and $fieldNames,
  34. #               'sortByAuthors' can now sort using last author first,
  35. #                   and is a bit faster,
  36. #               'formatEntry' rewrites entries in canonical format,
  37. #               More customization of canonical format allowed ('indentString')
  38. #               Bib mode definition adapted to Alpha 5.90.
  39. # 1.5 (7/94)    "sortByAuthors" is now robust (I think),
  40. #               Mode of new windows now set correctly.
  41. # 1.4 (7/94)    Added sorting by authors, but still only semi-functional,
  42. #               Added regexp searching by field,
  43. #               "getEntry" bugs fixed.
  44. # 1.2 (7/94)    Bib mode definition adapted to Alpha 5.85,
  45. #               Added bib-file marking (bibMarkFile),
  46. #               Entry and field creation now controlled by data arrays.
  47. # 1.1 (6/94)    Custom BibTeX icon, 
  48. #               Added simple search capability (matchingEntries).
  49. # 1.0 (9/93)    First stable version.
  50. #
  51. ###########################################################################
  52. # This package was inspired by the LaTeX package (latex.tcl), written by
  53. #    Richard T. Austin  <austin@eecs.umich.edu>  , and (currently),
  54. #    Tom Scavo          <scavo@syr.edu>
  55. #
  56. ###########################################################################
  57. ###########################################################################
  58. # BibTeX Key Bindings.
  59. ###########################################################################
  60. # abbreviations:  <o> = option, <z> = control, <s> = shift, <c> = command
  61. #
  62. bind 'b' <sz>    selectEntry "Bib"
  63. bind 'n' <sz>    nextEntry "Bib"
  64. bind 'p' <sz>    prevEntry "Bib"
  65.  
  66. bind 'f' <sz>    searchFields "Bib"
  67. bind 'm' <sz>    searchEntries "Bib"
  68. bind 'l' <sz>    formatEntry "Bib"
  69.  
  70. # tab stops:
  71. bind    '\t'    nextTabStop    "Bib"
  72. bind    '\t'    <s>     prevTabStop    "Bib"
  73. bind  '\t'  <z>  {nthTabStop 0}  "Bib"
  74. bind  '\t'  <c>  clearTabStops  "Bib"
  75.  
  76. ###########################################################################
  77. # Data Definitions
  78. ###########################################################################
  79. ###########################################################################
  80. # Define the data arrays that contain the names of the required,
  81. # optional, and preferred fields for each entry type.
  82. #
  83. # The index names of the rqdFld() array _define_ the valid entry types
  84. # recognized by the program.
  85. #
  86. set rqdFld(article) {author title journal year} 
  87. set optFld(article) {volume number pages month note}
  88. set myFld(article) {author title journal volume pages year note} 
  89.  
  90. set rqdFld(book) {author title publisher year} 
  91. set optFld(book) {editor volume number series address edition month note}
  92.  
  93. set rqdFld(booklet) {title} 
  94. set optFld(booklet) {author howpublished address month year note}
  95.  
  96. set rqdFld(conference) {author title booktitle year} 
  97. set optFld(conference) {editor volume number series pages organization publisher address month note}
  98.  
  99. set rqdFld(inBook) {author title chapter publisher year} 
  100. set optFld(inBook) {editor pages volume number series address edition month type note}
  101.  
  102. set rqdFld(inCollection) {author title booktitle publisher year} 
  103. set optFld(inCollection) {editor volume number series type chapter pages address edition month note}
  104.  
  105. set rqdFld(inProceedings) {author title booktitle year} 
  106. set optFld(inProceedings) {editor volume number series pages organization publisher address month note}
  107.  
  108. set rqdFld(manual) {title} 
  109. set optFld(manual) {author organization address edition year month note}
  110.  
  111. set rqdFld(mastersThesis) {author title school year} 
  112. set optFld(mastersThesis) {address month note type}
  113.  
  114. set rqdFld(misc) {} 
  115. set optFld(misc) {author title howpublished year month note}
  116.  
  117. set rqdFld(phdThesis) {author title school year} 
  118. set optFld(phdThesis) {address month type note}
  119.  
  120. set rqdFld(proceedings) {title year} 
  121. set optFld(proceedings) {editor volume number series publisher organization address month note}
  122.  
  123. set rqdFld(techReport) {author title institution year} 
  124. set optFld(techReport) {type number address month note}
  125.  
  126. set rqdFld(unpublished) {author title note} 
  127. set optFld(unpublished) {year month}
  128.  
  129. set entryNames [lsort [array names rqdFld]]
  130. set customEntries [lsort [array names myFld]]
  131.  
  132. ###########################################################################
  133. # Define an array of flags indicating whether the data a given field
  134. # type should be quoted.  The actual characters used to quote the field are
  135. # given by $bibOpenQuote and $bibCloseQuote, which are set by the routine
  136. # 'bibFieldDelims' according to the flag $fieldBraces.
  137. #
  138. # Note that the index names of the useBrace() array _define_ the valid 
  139. # field types recognized by the program.
  140. #
  141. set useBrace(address)    1
  142. set useBrace(annote)    1
  143. set useBrace(author)     1
  144. set useBrace(booktitle)    1
  145. set useBrace(chapter)    0
  146. set useBrace(edition)    1
  147. set useBrace(editor)    1
  148. set useBrace(howpublished)    1
  149. set useBrace(institution)    1
  150. set useBrace(journal)    1
  151. set useBrace(key)    1
  152. set useBrace(language)    1
  153. set useBrace(month)    1
  154. set useBrace(note)    1
  155. set useBrace(number)    0
  156. set useBrace(organization)    1
  157. set useBrace(pages)    0
  158. set useBrace(publisher)    1
  159. set useBrace(school)    1
  160. set useBrace(series)    1
  161. set useBrace(title)    1
  162. set useBrace(type)    1
  163. set useBrace(volume)    0
  164. set useBrace(year)    0
  165.  
  166. set fieldNames [lsort [array names useBrace]]
  167. ###########################################################################
  168. # Default values for newly created fields
  169. #
  170. set defFldVal(language) "german"
  171.  
  172. set fieldDefs [lsort [array names defFldVal]]
  173.  
  174. ###########################################################################
  175. # BibTeX-mode mode definition
  176. ###########################################################################
  177. newModeVar Bib suffixString    { \\\\} 0
  178. newModeVar Bib prefixString    {% } 0
  179. newModeVar Bib wordWrap        {0} 1
  180. newModeVar Bib wordBreak        {[a-zA-Z0-9]+} 0
  181. newModeVar Bib wordBreakPreface        {[^a-zA-Z0-9]} 0
  182. newModeVar Bib funcExpr        {[ \t]*@[a-zA-Z]+.([a-zA-Z0-9]+)} 0
  183. newModeVar Bib optionIsMeta    {1} 1
  184.  
  185. newModeVar Bib overwriteBuffer {1} 1
  186. newModeVar Bib fieldBraces {1} 1
  187. newModeVar Bib entryBraces {1} 1
  188. newModeVar Bib indentString {   } 0
  189.  
  190. set bibtexKeyWords {address annote author booktitle 
  191.     chapter city crossref edition editor howpublished institution 
  192.     journal key language month note number organization 
  193.     publisher pages school series title type 
  194.     volume year}
  195. regModeKeywords -e {%} -m {@} -c red -k blue Bib $bibtexKeyWords
  196. unset bibtexKeyWords
  197.  
  198. ###########################################################################
  199. # BibTeX Menu Definition.
  200. ###########################################################################
  201. set bibtexMenu "•136"
  202.  
  203. proc bibtex {} {
  204.     global bibtexPath
  205.     set name [checkRunning BibTeX BIBt bibtexPath]
  206.     if {![string length $name]} return
  207.     switchTo $name
  208. }
  209.  
  210. proc makeindex {} {
  211.     global makeindexPath
  212.     set name [checkRunning MakeIndex Midx makeindexPath]
  213.     if {![string length $name]} return
  214.     switchTo $name
  215. }
  216.  
  217. menu -n $bibtexMenu {
  218.     "bibtex"
  219.     "(-)"  
  220.     {menu -n Entries -p makeEntry {}
  221.     }
  222.     {menu -n Fields -p makeField {}
  223.     }
  224.     "(-)"
  225.     "selectEntry"
  226.     "nextEntry"
  227.     "prevEntry"
  228.     "formatEntry"
  229.     "(-)"
  230.     "searchEntries"
  231.     "searchFields"
  232.     {menu -n sortBy... -p bibSortProc {
  233.         "citeKey"
  234.         "firstAuthor"
  235.         "lastAuthor"
  236.         }
  237.     }
  238.     
  239. menu -n Entries -p makeEntry [concat $entryNames {
  240.         "(-)"
  241.         "customEntry"
  242.         } ]
  243.  
  244. menu -n Fields -p makeField [concat $fieldNames {
  245.         "(-)"
  246.         "customField"
  247.         "multipleFields"
  248.         } ]
  249.         
  250. ###########################################################################
  251. # Menu command procs
  252. ###########################################################################
  253.         
  254. proc makeField {menu item} {
  255.     global fieldNames
  256.     bibFormatSetup
  257.     
  258.     if {$item == "multipleFields"} then {
  259.         set flds [listpick -l -L {author year} -p "Pick desired fields:" $fieldNames]
  260.         if {[llength flds]} {
  261.             set lines {}
  262.             foreach fld $flds {
  263.                 append lines [newField $fld]
  264.             }
  265.         } else {
  266.             return
  267.         }
  268.     } else {
  269.         set lines [newField $item]
  270.     }
  271.     
  272.     set pos0 [nextLineStart [getPos]]
  273.     goto $pos0
  274.     insertText $lines
  275.     goto $pos0
  276.     nextTabStop
  277. }
  278.  
  279. proc makeEntry {menu item} {
  280.     bibFormatSetup
  281.     newEntry $item
  282. }
  283.  
  284. ###########################################################################
  285. #  Return the bounds of the bibliographic entry surrounding the current 
  286. #  position.
  287. #
  288. proc getEntry {pos} {
  289.     
  290.     set pos1 [search -f 0 -r 1 -n {[     ]*@[a-zA-Z]*[\{\(]} $pos ]
  291.     if {$pos1 == ""} then {
  292.         set begPos [nextLineStart $pos]
  293.         set endPos $begPos
  294.     } else {
  295.         set begPos [lineStart [lindex $pos1 0]]
  296.         set pos0 [lindex $pos1 1]
  297. #         set pos1 [matchIt "\{" $pos0]]
  298.         set openBrace [getText [expr $pos0-1] $pos0 ]
  299.         set pos1 [matchIt $openBrace $pos0]]
  300.         set endPos [nextLineStart $pos1]
  301.     }
  302.     return [list $begPos $endPos]
  303. }
  304.  
  305. ###########################################################################
  306. #  Advance to the next bibliographic entry.
  307. #
  308. proc nextEntry {} {
  309.     saveVars
  310.     
  311.     set pos0 [lindex [getEntry [getPos]] 1]
  312.     
  313.     set pos [search -f 1 -r 1 -n {[     ]*@[a-zA-Z]+[\{\(]} $pos0 ]
  314.     if {$pos == ""} then {
  315.        set nextPos $pos0
  316.     } else {
  317.        set nextPos [lindex $pos 0]
  318.     }
  319.     goto $nextPos
  320. }
  321.  
  322. ###########################################################################
  323. #  Go back to the previous bibliographic entry.
  324. #
  325. proc prevEntry {} {
  326.     saveVars
  327.     
  328.     set pos0 [lindex [getEntry [getPos]] 0]
  329.     set pos1 $pos0
  330.     if {$pos1 > 0} {incr pos1 -1}
  331.     
  332.     set pos [search -f 0 -r 1 -n {[     ]*@[a-zA-Z]+[\{\(]} $pos1 ]
  333.     if {$pos == ""} then {
  334.        set nextPos $pos0
  335.     } else {
  336.        set nextPos [lindex $pos 0]
  337.     }
  338.     goto $nextPos
  339. }
  340.  
  341. ###########################################################################
  342. #  Select (highlight) the current bibliographic entry.
  343. #
  344. proc selectEntry {} {
  345.     set pos [getEntry [getPos]]
  346.     select [lindex $pos 0] [lindex $pos 1]
  347. }
  348.  
  349. ###########################################################################
  350. #  Create a new bibliographic entry with its required fields.
  351. #
  352. proc newEntry {entryName} {    
  353.     global  entryNames customEntries fieldNames rqdFld optFld myFld defFldVal
  354.     global bibOpenEntry bibCloseEntry
  355.     goto [lindex [getEntry [getPos]] 1]
  356.     if {$entryName == "customEntry"} {
  357.         set lines "@•$bibOpenEntry•,\r"
  358.         set theFields [listpick -l -L {author} -p "Pick desired fields:" $fieldNames]
  359.     } else {
  360.         set lines "@${entryName}$bibOpenEntry•,\r"
  361.         if {[lsearch -exact $customEntries $entryName] >= 0 && [llength $myFld($entryName)]} {
  362.             set theFields $myFld($entryName)
  363.         } elseif {[lsearch -exact $entryNames $entryName] >= 0} {
  364.             set theFields $rqdFld($entryName)
  365.         } else {
  366.             set theFields {}
  367.         }
  368.     }
  369.     set theTop [lineStart [getPos]]
  370.     foreach field $theFields {
  371.         catch {append lines [newField $field]}
  372.     }
  373.     append lines "$bibCloseEntry\r"
  374.     insertText $lines
  375.     goto $theTop
  376.     nextTabStop
  377. }
  378.  
  379. ###########################################################################
  380. #  Create a new field within the current bibliographic entry
  381. #
  382. proc newField {fieldName} {    
  383.     global fieldNames useBrace bibOpenQuote bibCloseQuote bibIndent
  384.     global fieldDefs defFldVal
  385.     if {[lsearch -exact $fieldNames $fieldName] >= 0} {
  386.         set needBraces $useBrace($fieldName)
  387.     } else {
  388.         set needBraces 1
  389.     }
  390.     
  391.     if {[lsearch -exact $fieldDefs $fieldName] >= 0} {
  392.         set val $defFldVal($fieldName)
  393.     } else {
  394.         set val "•"
  395.     }
  396.  
  397.     if {$needBraces || $fieldName == "customField"} {
  398.         set result "$bibIndent$fieldName =      ${bibOpenQuote}${val}${bibCloseQuote},\r"
  399.     } else {
  400.         set result "$bibIndent$fieldName =      $val,\r"
  401.     }    
  402.     return $result
  403. }
  404.  
  405. proc bibFormatSetup {} {
  406.     global bibOpenQuote bibCloseQuote bibIndent BibmodeVars
  407.     global bibOpenEntry bibCloseEntry
  408.     bibFieldDelims
  409.     bibEntryDelims
  410.     set bibIndent $BibmodeVars(indentString)
  411.     regsub {\\t} $bibIndent {    } bibIndent
  412. }
  413.  
  414. ###########################################################################
  415. #  Find all entries that match a given regular expression and copy them to 
  416. #  a new buffer.
  417. #
  418. proc searchEntries {} {
  419.     if [catch {prompt "Regular expression:" ""} reg] return
  420.     if {![string length $reg]} return
  421.     set reg ^.*$reg.*$
  422.     
  423.     set matches [findEntries $reg]
  424.     if {[llength $matches] >0} {
  425.         writeEntries $matches 0
  426.     } else {
  427.         message "No matching entries were found"
  428.     }
  429. }
  430.  
  431. ###########################################################################
  432. #  Find all entries in which the indicated field matches a given regular 
  433. #  expression and copy them to a new buffer.  
  434. #
  435. proc searchFields {} {
  436.     global fieldNames
  437.     if {[catch {eval prompt {{Field name:}} "author" {Fields} $fieldNames} fld]} return
  438.     if {![string length $fld]} return
  439.  
  440.     if {[catch {prompt "Regular expression:" ""} reg]} return
  441.     if {![string length $reg]} return
  442.  
  443.     set matches [findEntries $reg]
  444.     if {[llength $matches] == 0} {
  445.         return "No matching entries were found"
  446.     }
  447.     
  448.     set vals {}
  449.     foreach hit $matches {
  450.         set pos [lindex $hit  1]
  451.         set top [lindex $hit 2] 
  452.         set bottom [lindex $hit 3]
  453.         if {[getFldName $pos $top] == $fld} {
  454.             lappend vals [list $top $bottom]
  455.         }
  456.     }
  457.     
  458.     if {[llength $vals] >0} {
  459.         writeEntries $vals 0
  460.     } else {
  461.         message "No matching entries were found"
  462.     }
  463.     
  464. }
  465.  
  466. ###########################################################################
  467. # Sort all of the entries one of various criteria.
  468. #
  469. proc bibSortProc {menu item} {
  470.     if {$item == "citeKey"} {
  471.         sortByCiteKey
  472.     } elseif  {$item == "firstAuthor"} {
  473.         sortByAuthors 0
  474.     } elseif  {$item == "lastAuthor"} {
  475.         sortByAuthors 1
  476.     }
  477. }
  478.  
  479. ###########################################################################
  480. # Sort all of the entries in the file alphabetically by author.
  481. #
  482. proc sortByAuthors {lastAuthorFirst} {
  483.     set matches [findEntries {^[    ]*@[^\{\(]+[\{\(]([-A-Za-z0-9_:\.]+)} ]
  484.     set vals {}
  485.     set others {}
  486.     set beg [maxPos]
  487.     set end 0
  488.     foreach hit $matches {
  489.         set pos [lindex $hit 1]
  490.         set top [lindex $hit 2] 
  491.         set bottom [lindex $hit 3]
  492.         set entry [getText $top $bottom]
  493.         regsub -all "\[\n\r\]+" $entry { } entry
  494.         regsub -all "\[ \t\]\[ \t\]+" $entry { } entry
  495.         if {![catch {getFldValue $entry author} fldval]} {
  496.             lappend vals [list [authSortKey $fldval $lastAuthorFirst] $top $bottom]
  497.         } else {
  498.             lappend others [list $pos $top $bottom]
  499.         }
  500.         if {$top < $beg} {set beg $top}
  501.         if {$bottom > $end} {set end $bottom}
  502.     }
  503.     set result [concat $others [lsort $vals]]
  504.     if {[llength $result] >0} {
  505.         writeEntries $result 1 $beg $end
  506.     } else {
  507.         message "No results of author sort !!??"
  508.     }
  509. }
  510.  
  511. ###########################################################################
  512. # Create a sort key from an author list.  When sorting entries by author, 
  513. # performing the sort using keys should be faster than reparsing the author 
  514. # lists for every comparison (the old method :-( ).
  515. #
  516. proc authSortKey {authList lastAuthorFirst} {
  517.     set pat1 {\\.\{([A-Za-z])\}}
  518.     set pat2 {\{([^\{\}]+) ([^\{\}]+)\}}
  519.  
  520. # Remove enclosing braces, quotes, or whitespace
  521.     set auths %[string trim $authList {{}"     }]&
  522. # Remove TeX codes for accented characters
  523.     regsub -all $pat1 $auths {\1} auths
  524. # Concatenate strings enclosed in braces
  525.     while {[regsub -all $pat2 $auths {{\1\2}} auths]} {}
  526. # Remove braces (curly and square)
  527.     regsub -all {[][\{\}]} $auths {} auths
  528. #    regsub -all {,} $auths { ,} auths
  529. # Replace 'and's with begin-name/end-name delimiters
  530.     regsub -all {[ \t]and[ \t]} $auths { \&% } auths
  531. # Put last name first in name fields without commas
  532.     regsub -all {%([^\&,]+) ([^\&, ]+) *\&} $auths {%\2,\1\&} auths
  533. # Remove begin-name delimiters
  534.     regsub -all {%} $auths {} auths
  535. # Remove whitespace surrounding name separators
  536.     regsub -all {[ \t]*\&[ \t]*} $auths {\&} auths
  537. # Replace whitespace separating words with shrieks 
  538.     regsub -all {[ \t,]+} $auths {!} auths
  539. # If desired, move last author to head of sort key
  540.     if {$lastAuthorFirst} {
  541.         regsub {(.*)&([^&]+)} $auths {\2!\1} auths
  542.     }
  543.         
  544.     return $auths
  545. }
  546.  
  547. ###########################################################################
  548. # Sort all of the entries in the file alphabetically by their cite-keys.
  549. #
  550. proc sortByCiteKey {} {
  551.  
  552.     set matches [findEntries {^[    ]*@[^\{\(]+[\{\(]([-A-Za-z0-9_:\.]+)} ]
  553.     set begEntries [maxPos]
  554.     set endEntries 0
  555.     foreach hit $matches {
  556.         set beg [lindex $hit 0]
  557.         set end [lindex $hit 1]
  558.         set top [lindex $hit 2] 
  559.         set bottom [lindex $hit 3]
  560.         regexp {[\{\(]([-A-Za-z0-9_:\.]+)} [getText $beg $end] allofit citekey
  561.         lappend vals [list $citekey $top $bottom]]
  562.         if {$top < $begEntries} {set begEntries $top}
  563.         if {$bottom > $endEntries} {set endEntries $bottom}
  564.     }
  565.  
  566.     set result [lsort $vals]
  567.     if {[llength $result] >0} {
  568.         writeEntries $result 1 $begEntries $endEntries
  569.     } else {
  570.         message "No results of cite-key sort !!??"
  571.     }
  572. }
  573.  
  574. ###########################################################################
  575. # Search for all entries matching a given regular expression.  The results
  576. # are returned in a list, each element of which is a list of four integers:
  577. # the beginning and end of the matching entry and the beginning and end of
  578. # the matching string.  Adapted from "matchingLines" in "misc.tcl".
  579. #
  580. proc findEntries {reg} {
  581.     if {![string length $reg]} return
  582.     
  583.     set pos 0   
  584.     set result {}                             
  585.     while {![catch {search -f 1 -r 1 -m 0 -i 1 $reg $pos} mtch]} {
  586.         lappend result [concat  $mtch [getEntry [lindex $mtch 0]]]
  587.         set pos [lindex $mtch 1]
  588.     }
  589.     return $result
  590. }
  591.  
  592. ###########################################################################
  593. #  Return a list containing the data for the current entry, indexed by
  594. #  the parameter names, e.g., "author", "year", etc.  Index names for the 
  595. #  entry type and cite-key are "type" and "citekey". 
  596. #
  597. proc getFields {pos} {
  598. #    set topPat {@([a-zA-Z]+)\{([A-Za-z0-9]+),}
  599.     set topPat {[     ]*@([a-zA-Z]+)[\{\(]([-A-Za-z0-9_:\.]+)[     ]*,}
  600.     set fldPat {[     ]*([a-zA-Z]+)[     ]*=[     ]*}
  601.  
  602.     set limits [getEntry $pos]
  603.     set top [lindex $limits 0]
  604.     set bottom [lindex $limits 1]
  605.     
  606.     set entry [getText $top $bottom]
  607.     regsub -all "\[\n\r\]+" $entry { } entry
  608.     regsub -all "\[ \t\]\[ \t\]+" $entry { } entry
  609.  
  610.     if {[regexp  -indices $topPat $entry mtch theType theKey ]} {
  611.         lappend names type
  612.         set type [string tolower [string range $entry [lindex $theType 0] [lindex $theType 1]]]
  613.         lappend data [list $type]
  614.         lappend names citekey
  615.         set key [string range $entry [lindex $theKey 0] [lindex $theKey 1]]
  616.         lappend data $key
  617.         
  618.         set entry [string range $entry [expr 1 + [lindex $mtch 1]] end]
  619.         while {![catch {getField $entry} res]} {
  620.             lappend names [string tolower [lindex $res 0]]
  621.             lappend data [breakIntoLines [lindex $res 1]]
  622.             set entry [lindex $res 2]
  623.         }
  624.         return [list $names $data]
  625.     } else {
  626.         error "Invalid entry"
  627.     }
  628. }
  629.  
  630. ###########################################################################
  631. #  Extract the next data field from the entry, passed as a single string.
  632. #  This version tries to be completely general, allowing nested braces
  633. #  within data fields and ignoring escaped delimiters (mainly \").  It's
  634. #  probably unnecessarily slow as a result :-(
  635. #
  636. proc getField {entry} {
  637.     set fldPat {[     ]*([^ =,]+)[     ]*=[     ]*}
  638.     set slash "\\"
  639.     set qslash "\\\\"
  640.     
  641.     set ok [regexp -indices -nocase $fldPat $entry mtch sub1]
  642.     if {$ok} {
  643.         set name [string range $entry [lindex $sub1 0] [lindex $sub1 1]]
  644.         set pos [expr [lindex $mtch 1] + 1]
  645.         set delim [string range $entry $pos $pos]
  646.         set entry [string range $entry [expr 1 + $pos] end]
  647.         
  648.         if {$delim == {"}} {
  649.             set ck $qslash
  650.             set fld ""
  651.             while {$ck == $qslash} {
  652.                 set ok [regexp -indices {^([^"]*)"} $entry mtch sub1]
  653.                 if {$ok} {
  654.                     append fld [string range $entry [lindex $mtch 0] [lindex $mtch 1]]
  655.                     set ck $slash[string range $entry [lindex $sub1 1] [lindex $sub1 1]]
  656.                     set pos [expr 1 + [lindex $mtch 1]]
  657.                     set entry [string range $entry $pos end]
  658.                 } else {
  659.                     error "Couldn't match quote as field delimiter"
  660.                 }
  661.             }
  662.             set pos [expr [string length $fld] - 2]
  663.             set fld [string range $fld 0 $pos]
  664.             set ok [regexp -indices {^([^,]*),} $entry mtch sub1]
  665.             if {$ok} {
  666.                 set entry [string range $entry [expr 1 + [lindex $mtch 1]] end]
  667.             }
  668.             
  669.         } elseif {$delim == "\{"} {
  670.         
  671.             set nopen 1
  672.             set nclose 0
  673.             set fld ""
  674.             while {$nopen - $nclose != 0} {
  675.                 set ok [regexp -indices "^\[^\}\]*\}" $entry mtch]
  676.                 if {$ok} {
  677.                     append fld [string range $entry [lindex $mtch 0] [lindex $mtch 1]]
  678.                     set entry [string range $entry [expr 1 + [lindex $mtch 1]] end]
  679.                     set nopen [llength [split $fld "\{"]]
  680.                     incr nclose
  681.                 } else {
  682.                     error "Couldn't match brace as field delimiter"
  683.                 } 
  684.             }
  685.             set pos [expr [string length $fld] - 2]
  686.             set fld [string range $fld 0 $pos]
  687.             set ok [regexp -indices {^([^,]*),} $entry mtch sub1]
  688.             if {$ok} {
  689.                 set entry [string range $entry [expr 1 + [lindex $mtch 1]] end]
  690.             }
  691.             
  692.         } else {
  693.         
  694.             set entry ${delim}${entry}
  695.             set ok [regexp -indices {^([^,]*),?} $entry mtch sub1]
  696.             if {$ok} {
  697.                 set fld [string range $entry [lindex $sub1 0] [lindex $sub1 1]]
  698.                 set pos [expr 1 + [lindex $mtch 1]]
  699.                 set entry [string range $entry $pos end]
  700.                 set entry [string trimleft $entry ","]
  701.             } else {
  702.                 set fld [string trimright $entry]
  703.                 set entry ""
  704.             }
  705.         }
  706.         return [list $name $fld $entry]
  707.     } else {
  708.         error "No more fields in this entry"
  709.     }
  710. }
  711.  
  712. ###########################################################################
  713. # Extract the data from the indicated field of an entry, which is passed 
  714. # as a single string.  This version tries to be completely general, 
  715. # allowing nested braces within data fields and ignoring escaped 
  716. # delimiters.  (derived from proc getField).
  717. #
  718. proc getFldValue {entry fldname} {
  719.     set fldPat "\[     \]*${fldname}\[     \]*=\[     \]*"
  720.     set slash "\\"
  721.     set qslash "\\\\"
  722.     
  723.     set ok [regexp -indices -nocase $fldPat $entry mtch]
  724.     if {$ok} {
  725.         set pos [expr [lindex $mtch 1] + 1]
  726.         set delim [string range $entry $pos $pos]
  727.         set entry [string range $entry [expr 1 + $pos] end]
  728.         
  729.         if {$delim == {"}} {
  730.             set ck $qslash
  731.             set fld ""
  732.             while {$ck == $qslash} {
  733.                 set ok [regexp -indices {^([^"]*)"} $entry mtch sub1]
  734.                 if {$ok} {
  735.                     append fld [string range $entry [lindex $mtch 0] [lindex $mtch 1]]
  736.                     set ck $slash[string range $entry [lindex $sub1 1] [lindex $sub1 1]]
  737.                     set pos [expr 1 + [lindex $mtch 1]]
  738.                     set entry [string range $entry $pos end]
  739.                 } else {
  740.                     error "Couldn't match quote as field delimiter"
  741.                 }
  742.             }
  743.             set fld [string trimright $fld {\"}]
  744.                         
  745.         } elseif {$delim == "\{"} {
  746.         
  747.             set nopen 1
  748.             set nclose 0
  749.             set fld ""
  750.             while {$nopen - $nclose != 0} {
  751.                 set ok [regexp -indices "^\[^\}\]*\}" $entry mtch]
  752.                 if {$ok} {
  753.                     append fld [string range $entry [lindex $mtch 0] [lindex $mtch 1]]
  754.                     set entry [string range $entry [expr 1 + [lindex $mtch 1]] end]
  755.                     set nopen [llength [split $fld "\{"]]
  756.                     incr nclose
  757.                 } else {
  758.                     error "Couldn't match brace as field delimiter"
  759.                 } 
  760.             }
  761.             set fld [string trimright $fld "\}"]
  762.             
  763.         } else {
  764.         
  765.             set entry ${delim}${entry}
  766.             set ok [regexp -indices {^([^,]*),?} $entry mtch sub1]
  767.             if {$ok} {
  768.                 set fld [string range $entry [lindex $sub1 0] [lindex $sub1 1]]
  769.                 set pos [expr 1 + [lindex $mtch 1]]
  770.                 set entry [string range $entry $pos end]
  771.             } else {
  772.                 set fld [string trimright $entry]
  773.             }
  774.         }
  775.  
  776.         return $fld
  777.         
  778.     } else {
  779.         error "field not found"
  780.     }
  781. }
  782.  
  783. ###########################################################################
  784. # Parse the entry around position "pos" and rewrite it to the original 
  785. # buffer in a canonical format
  786. #
  787. proc formatEntry {} {
  788.     global useBrace bibOpenQuote bibCloseQuote 
  789.     global bibOpenEntry bibCloseEntry bibIndent
  790.     
  791.     bibFormatSetup
  792.     
  793.     set pos [getPos]
  794.     set limits [getEntry $pos]
  795.     set top [lindex $limits 0]
  796.     set bottom [lindex $limits 1]
  797.     if {![catch {getFields $pos} flds]} {
  798.         set names [lindex $flds 0]
  799.         set vals [lindex $flds 1]
  800.         set nfld [llength $names]
  801.         set lines {}
  802.         
  803.         append lines "@[lindex $vals 0]${bibOpenEntry}[lindex $vals 1],\r"
  804.         for {set ifld 2} {$ifld < $nfld} {incr ifld} { 
  805.             set nm [lindex $names $ifld]
  806.             set vl [lindex $vals $ifld]
  807.             
  808.             set pref "${bibIndent}$nm = "
  809.             regsub -all {.} $pref { } ind
  810.             
  811.             set ok [expr ! [catch {set useit $useBrace($nm)}]]
  812.             if { $ok && $useit == 0 && [isNum $vl]} {
  813.                 set vl "$vl,"
  814.             } else {
  815.                 set vl "${bibOpenQuote}${vl}${bibCloseQuote},"
  816.             }
  817.             
  818.             set pieces [split $vl "\r"]
  819.             append lines "$pref    [lindex $pieces 0]\r"
  820.             foreach piece [lrange $pieces 1 end] {
  821.             append lines "$ind    $piece\r"
  822.             }
  823.         }
  824.         append lines "$bibCloseEntry\r"
  825.         deleteText $top $bottom 
  826.         goto $top 
  827.         insertText $lines
  828.     }
  829. }
  830.  
  831. ###########################################################################
  832. # Get the name of the field that starts before the given position,  
  833. # $pos.  The positions $top and $bottom restrict the range of the 
  834. # search for the beginning and end of the field; typically, $top and
  835. # $bottom will be the limits of a given entry.
  836. #
  837. proc getFldName {pos top} {
  838.     set fldPat {[     ]*([a-zA-Z]+)[     ]*=[     ]*}
  839.     if {![catch {search -f 0 -r 1 -m 0 -i 1 -limit $top "^$fldPat" $pos} mtch]} {
  840.         set theText [eval getText $mtch]
  841.         regexp -nocase $fldPat $theText allofit fldnam
  842.         return $fldnam
  843.     } else {
  844.         return {}
  845.     }
  846. }
  847.  
  848. ###########################################################################
  849. #  Set the quote characters for quoted fields based on the value of the 
  850. #  flag $bibUseBrace
  851. proc bibFieldDelims {} {
  852.     global BibmodeVars bibOpenQuote bibCloseQuote
  853.     if {$BibmodeVars(fieldBraces)} then {
  854.         set bibOpenQuote "{"
  855.         set bibCloseQuote "}" 
  856.     } else {
  857.         set bibOpenQuote {"} 
  858.         set bibCloseQuote {"} 
  859.     }
  860. }
  861.  
  862. proc bibEntryDelims {} {
  863.     global BibmodeVars bibOpenEntry bibCloseEntry
  864.     if {$BibmodeVars(entryBraces)} then {
  865.         set bibOpenEntry "{"
  866.         set bibCloseEntry "}" 
  867.     } else {
  868.         set bibOpenEntry "("
  869.         set bibCloseEntry ")"
  870.     }
  871. }
  872.  
  873. proc isBibFile {} {
  874.     set fileName [lindex [winNames -f] 0]   
  875.     set ext [file extension $fileName]
  876.     return [string match ".bib" [string tolower $ext]] 
  877. }
  878.  
  879. proc hasNumVal {str} {
  880.     expr ! [catch {expr $str}]
  881. }
  882. proc isNum {str} {
  883.     regexp {^[     ]*[0-9]+[     ]*$} $str mtch
  884. }
  885.  
  886. ###########################################################################
  887. # Take a list of lists that point to selected entries and copy these into
  888. # a new window.  The beginning and ending positions for each entry must 
  889. # be the last two items in each sublist.  The rest of the sublists are
  890. # ignored.  It is assumed that each sublist has the same number of items.
  891. #
  892. proc writeEntries {entryPos nondestructive {beg {0}} {end {-1}}} {
  893.         global BibmodeVars
  894.         if {$end < 0} {set end [maxPos]}
  895.         set llen [expr [llength [lindex $entryPos 0]] - 1]
  896.         set llen1 [expr $llen-1]
  897.         foreach entry $entryPos {
  898.             set limits [lrange $entry $llen1 $llen]
  899.             append lines [eval getText $limits]
  900.         }
  901.         set overwriteOK [expr $nondestructive || ! [isBibFile]]
  902.         if {$BibmodeVars(overwriteBuffer) && $overwriteOK} {
  903.             deleteText $beg $end
  904.             insertText $lines
  905.             goto $beg
  906.         } else {
  907.             set begLines [getText 0 [lineStart $beg]]
  908.             set endLines [getText [nextLineStart $end] [maxPos]]
  909.             new -n {*BibTeX Sort/Search*}
  910.             newMode Bib
  911.             insertText $begLines
  912.             insertText $lines
  913.             insertText $endLines
  914.             goto $beg
  915.             setWinInfo dirty 0
  916.             catch shrinkWindow
  917.         }
  918. }
  919.  
  920. ###########################################################################
  921. # Set a named mark for each entry, using the cite-key name
  922. #
  923. proc BibMarkFile {} {
  924.     set topPat {^[     ]*@[a-zA-Z]+[\{\(]([-A-Za-z0-9_:\.]+)}
  925.     set pos 0
  926.     while {![catch {search -f 1 -r 1 -m 0 -i 0 $topPat $pos} res]} {
  927.         set start [lindex $res 0]
  928.         set end [nextLineStart $start]
  929.         set text [getText $start $end]
  930.         set lab ""
  931.         if {[regexp $topPat $text mtch entryTag]} {
  932.             set lab $entryTag
  933.             setNamedMark $lab [lineStart [expr $start - 1]] $start $start
  934.         }
  935.         set pos $end
  936.     }
  937. }
  938.  
  939. proc dummyBibTeX {} {
  940. }
  941.  
  942. ############################################################################
  943. # Cause latex.tcl to be loaded by calling a dummy procedure defined in that
  944. # file.  This is necessary to get the TeX menu.
  945. #
  946.  
  947. dummyTeX
  948.  
  949. #